/*
 * antiproswpevei ton komvo tou Chord
 */
package gr.aueb.mscis.chord;

import gr.aueb.mscis.Activities.ContentNodesActivity;
import gr.aueb.mscis.configuration.Config;
import gr.aueb.mscis.protocol.NodeIdentifier;
import gr.aueb.mscis.protocol.ProtocolType;
import gr.aueb.mscis.protocol.RemoteMessage;
import java.io.Serializable;
import android.os.Message;
import android.util.Log;

public class Node implements Runnable, Serializable {

	private static final long serialVersionUID = 1L;
	private NodeIdentifier successor;
	private NodeIdentifier predecessor;
	private NodeIdentifier nodeId;
	private RemoteMessageDaemon rDaemon;
	private FingerAgent fAgent;
	private StabilizeThread stabThread;
	public static NodeIdentifier[] fingerTable;
	public static NodeIdentifier[] successorList;

	public Node() {
		fingerTable = new NodeIdentifier[6];
		successorList = new NodeIdentifier[3];
	}

	public void NodeRun() {
		
		if (register()) {

			Log.v("register", "ekane CREATE_RING");
			
			// threads edw gia ta upoloipa.RemoteDaemon, Agent DHT klp..
			rDaemon = new RemoteMessageDaemon(this);
			Thread rDeamonThread = new Thread(rDaemon);
			rDeamonThread.start();

			fAgent = new FingerAgent(this);
			Thread fAgentThread = new Thread(fAgent);
			fAgentThread.start();
			
			stabThread = new StabilizeThread(this);
			Thread stabilize = new Thread(stabThread);
			stabilize.start();
			
			Log.v("ip:port", this.getNodeId().getAddr() + ":" + this.getNodeId().getPort());
		}

		else {
		}

	}

	public NodeIdentifier getNodeId() {
		return nodeId;
	}

	public void setNodeId(NodeIdentifier nodeId) {
		this.nodeId = nodeId;
	}

	/* register tou node sto tracker */
	private boolean register() {
		/*
		 * elegxos gia to an to port pou exei eisax8ei einai egguro, alliws
		 * epilegetai to default automata
		 */
		if (Config.listeningPort > 65535 || Config.listeningPort < 1000) {
			Config.listeningPort = Config.defaultPort;
		}
		
		/*
		 * dimiourgia node indentifier pou 8a exei mesa to port pou 8a
		 * akouei o kombos.Tin IP tha tin parei o tracker apo to socket
		 */
		/*
		 * to port to exei eisagei o xristis. To listening port einai global
		 * metabliti pou brisketai stin class RemoteMessageServer
		 */
		nodeId = new NodeIdentifier(Config.listeningPort);
		
		RemoteMessageAgent connect = new RemoteMessageAgent();
		RemoteMessage response = connect.register(nodeId);

		if (response == null) {
			Message msg = ContentNodesActivity.handlerGlobal.obtainMessage();
			// msg.what = UPDATE_IMAGE;
			msg.obj = "Couldn't connect to tracker";
			// msg.arg1 = index;
			ContentNodesActivity.handlerGlobal.sendMessage(msg);
			return false;
		}

		/* emfanizei ston node ti minima elave apo ton tracker */
		if (response.getProtocolType().equals(ProtocolType.CREATE_RING)) {
			/*
			 * o tracker apantaei ston komvo na kanei create kai tou stelnei ta
			 * stoixeia tou komvou
			 */
			/* dimiourgia ring */
			Log.v("register", "elave CREATE_RING");
			create();
			Log.v("register", "ekane CREATE_RING");
			return true;

		} else if (response.getProtocolType().equals(ProtocolType.JOIN_RING)) {
			/*
			 * join sto ring apo ton komvo pou 8a apantisei o tracker. Exei
			 * bre8ei o tuxaios komvos, o opoios 8a apantisei pisw to succesor
			 * pou tou neou komvou gia eisagwgi sto ring
			 */
			Log.v("register", "elave JOIN_RING");
			joinRing(response);
			return true;

		} else if (response.getProtocolType().equals(
				ProtocolType.ALREADY_IN_RING)) {
			Log.v("register", "elave ALREADY_IN_RING");
			/* do nothing */
			return false;
		}
		return false;
	}

	/* dimiourgia ring */
	private void create() {
		successor = this.getNodeId();
		predecessor = null;
	}

	/*
	 * join sto ring apo ton komvo pou 8a apantisei o tracker. Exei bre8ei o
	 * tuxaios komvos, o opoios 8a apantisei pisw to succesor pou tou neou
	 * komvou gia eisagwgi sto ring Ara stelnw mnm ston komvo pou apantise o
	 * tracker gia na mou kanei find_succesor(key_mou)
	 */
	private void joinRing(RemoteMessage rm) {
		RemoteMessageAgent connect = new RemoteMessageAgent();
		Message msg = ContentNodesActivity.handlerGlobal.obtainMessage();
		msg.obj = "Waiting for successor reply";
		ContentNodesActivity.handlerGlobal.sendMessage(msg);
		connect.findSuccessor(this.nodeId.getHash(), rm.getNodeId(), this.nodeId, -1);
		

	}

	public void run() {
		NodeRun();

	}

	public void stabilize() {

		Log.v("stabilize", "mphke sth stabilize");
		try {
			// MSG to get Predecessor
			RemoteMessageAgent connect = new RemoteMessageAgent();
			NodeIdentifier succPred = connect.getPredecessor(this);
			
			//an o komvos pou lamvanw einai endiamesos tote ton rythmizw ws neo successor
			if (Node.isBetween(succPred.getHash(), this.getNodeId().getHash(), this.getSuccessor().getHash()))
			{
				Log.v("stabilize", "allaxe ton successor mou");
				this.setSuccessor(succPred);
			}
			
			//edw ginetai notify aytomata (embedded in successor's code)
			
		}// try
		catch (NullPointerException npe) {
			// An exei pesei o successor, pairnei ton epomeno apo th lista twn triwn
			find_new_successor();
		}
		
		
	}
	
	public void notify(NodeIdentifier niTonos)
	{
		Log.v("notify", "mphke sth notify");
		if(this.getPredecessor() == null || Node.isBetween(niTonos.getHash(), this.getPredecessor().getHash(), this.getNodeId().getHash()))
		{
			Log.v("notify", "allaxe ton predecessor mou");
			this.setPredecessor(niTonos);
			//TODO redistribute_keys(niTonos)
		}
	}

	/**
	 * 
	 * @param id = key
	 * @param finalDestination = finalDestination
	 */
	public void find_successor(String id, NodeIdentifier finalDestination, int fingerPosition)
		{
			if (isBetween(id, this.nodeId.getHash(), this.successor.getHash()))
			{
				RemoteMessageAgent connect = new RemoteMessageAgent();
				connect.findSuccessorReply(this.getSuccessor(), finalDestination, fingerPosition);
			}
			else 
			{
				NodeIdentifier niTonos = closest_preceding_node(id);
				
				if (niTonos.equals(this.nodeId))
				{   
					//steile oti egw eimai o successor
					RemoteMessageAgent connect = new RemoteMessageAgent();
					connect.findSuccessorReply(niTonos, finalDestination, fingerPosition);
					return;
				}else
				{
					RemoteMessageAgent connect = new RemoteMessageAgent();
					connect.findSuccessor(id, niTonos, finalDestination, fingerPosition);	
				}
			}
		}

	public NodeIdentifier closest_preceding_node(String id) {
		for (int i = fingerTable.length; i >= 0; i--) {
			if (isBetween(fingerTable[i].getHash(), this.nodeId.getHash(), id)) {
				return fingerTable[i];
			}
		}
		return this.nodeId;
	}

	public NodeIdentifier getSuccessor() {
		return this.successor;
	}

	public void setSuccessor(NodeIdentifier newSuccessor) {
		this.successor = newSuccessor;
	}

	public void setPredecessor( NodeIdentifier predecessor) {
		this.predecessor = predecessor;
	}
	
	public NodeIdentifier getPredecessor() {
		return this.predecessor;
	}

	private void find_new_successor() {
		//TODO tha prepei na koitazei an einai alive, alliws tha pairnei ton epomeno
		this.successor = Node.successorList[1];
	}

	public static boolean isBetween(String kID, String xID, String yID) {
		if (xID.equals(yID))
			return true;

		return ((((xID.compareTo(kID)) > 0) && ((yID.compareTo(kID)) > 0) && ((xID
				.compareTo(yID)) > 0))

				||

				(((xID.compareTo(kID)) < 0) && ((yID.compareTo(kID)) > 0) && ((xID
						.compareTo(yID)) < 0))

		||

		(((xID.compareTo(kID)) < 0) && ((yID.compareTo(kID)) < 0) && ((xID
				.compareTo(yID)) > 0)));
	}
}
